package com.mischa.atc.tests;

import org.junit.Assert;
import org.junit.Test;

import java.util.List;
import java.util.ArrayList;

import com.mischa.atc.Aircraft;
import com.mischa.atc.Controller;
import com.mischa.atc.Coordinate;
import com.mischa.atc.Flight;
import com.mischa.atc.RadarScreen;
import com.mischa.atc.RadarScreenDrawer;


public class RadarScreenTest {
	private class RadarScreenDrawerDummy implements RadarScreenDrawer	
	{
		public int drawEventsReceived;
		public int outOfRangeEventsReceived;
		@SuppressWarnings("unused")
		public Flight flight;
		public float x;
		public float y;
		public List<Integer> sweepDirections; 
		
		public RadarScreenDrawerDummy() {
			sweepDirections = new ArrayList<Integer>(); 
		}
		
		@Override
		public void DrawFlight(Flight flight, float x, float y) {			
			this.drawEventsReceived++;
			this.flight = flight;
			this.x = x;
			this.y = y;
		}

		@Override
		public void sweeping(int heading) {
			sweepDirections.add(new Integer(heading));
		}

		@Override
		public void flightOutOfRange(Flight flight) {
			outOfRangeEventsReceived++;
		}	
	}

	@Test
	public void testThatSingleFlightIsUpdatedOnSweep() throws InterruptedException
	{
		RadarScreenDrawerDummy drawer = new RadarScreenDrawerDummy();
		
		Controller controller = new Controller();		
		controller.addFlight();
		
		RadarScreen screen = new RadarScreen(controller, drawer);
		screen.setDisplayRangeInNM(1);
		screen.sweep(360, 2);
				
		Assert.assertEquals(1, drawer.drawEventsReceived);
	}
	
	@Test
	public void testThatCallbackGivesCorrectRadarScreenCoordinates() throws InterruptedException
	{
		RadarScreenDrawerDummy drawer = new RadarScreenDrawerDummy();
		
		Controller controller = new Controller();
		controller.addFlight();
		
		RadarScreen screen = new RadarScreen(controller, drawer);
		screen.setDisplayRangeInNM(1);

		screen.sweep(360, 1);
		
		// Check that the distance from the origin corresponds to speed of 250 kts 
		// i.e. it is approximately 250 (nm/h) / (1/3600 h) 
		// Note that coordinates correspond to nm here, because we have set the 
		// display to 1 nm x 1 nm 
		double distanceFromOrigin = distance(drawer.x, drawer.y);		
		double expectedDistanceFromOrigin = 250 / 3600d;
		
		Assert.assertEquals(1, drawer.drawEventsReceived);
		Assert.assertEquals(expectedDistanceFromOrigin, distanceFromOrigin, 1E-6);
	}
	
	@Test
	public void testThatScreenCoordinatesAreScaledByRadarScreenRadius() throws InterruptedException
	{
		RadarScreenDrawerDummy drawer = new RadarScreenDrawerDummy();
		
		Controller controller = new Controller();		
		controller.addFlight();
		
		RadarScreen screen = new RadarScreen(controller, drawer);
		screen.setDisplayRangeInNM(25);
		
		screen.sweep(360, 1);
		
		// Check that the distance is scaled by a factor of 25
		// (as 100% now corresponds to 25 times the distance)
		
		double distanceFromOrigin = distance(drawer.x, drawer.y);
		double expectedDistanceFromOrigin = 250 / 3600d;		
		expectedDistanceFromOrigin /= 25;
		
		Assert.assertEquals(1, drawer.drawEventsReceived);
		Assert.assertEquals(expectedDistanceFromOrigin, distanceFromOrigin, 1E-6);
	}

	@Test
	public void testThatRadarSweepsInClockwiseDirection() throws InterruptedException
	{
		RadarScreenDrawerDummy drawer = new RadarScreenDrawerDummy();
		RadarScreen screen = new RadarScreen(new Controller(), drawer);
		
		for(int i = 0; i < 3; ++i)
			screen.sweep(10, 1);
		
		Assert.assertEquals(3, drawer.sweepDirections.size());
		// intentionally not using item 0, it is probably 360
		int earlierSweepDirection = drawer.sweepDirections.get(1);
		int laterSweepDirection = drawer.sweepDirections.get(2);		
		Assert.assertTrue(earlierSweepDirection < laterSweepDirection);
	}
		
	private double distance(double x, double y)
	{
		return Math.sqrt(x * x + y * y);
	}
	
	@Test
	public void testThatFlightGoesOutOfRangeAfterTimeJumpWithoutRaisingDrawEvent() 
	{
		RadarScreenDrawerDummy drawer = new RadarScreenDrawerDummy();
		
		Controller controller = new Controller();
		controller.addFlight();
		
		RadarScreen screen = new RadarScreen(controller, drawer);
		screen.setDisplayRangeInNM(1);
		
		// At 250 knots per hour, it takes 1/250 hour, or ~15 seconds, to go out of range		
		screen.sweep(360, 2 * 3600/250);
		Assert.assertEquals(0, drawer.drawEventsReceived);
		Assert.assertEquals(1, drawer.outOfRangeEventsReceived);
	}
	
	@Test
	public void testThatFlightGoesOutOfRangeAfterSeriesOfSweeps()
	{
		RadarScreenDrawerDummy drawer = new RadarScreenDrawerDummy();
		
		Controller controller = new Controller();
		controller.addFlight();
		
		RadarScreen screen = new RadarScreen(controller, drawer);
		screen.setDisplayRangeInNM(1);
				
		for(int time = 0; time < 20; ++time)
		{
			screen.sweep(360, 1);
		}
		
		// At 250 knots per hour, it takes 1/250 hour, or 14.something seconds, to go out of range
		// That means we get 14 draw events (t = 1, ..., 14), followed by one out-of-range event at t = 15
		Assert.assertEquals(3600/250, drawer.drawEventsReceived);
		Assert.assertEquals(1, drawer.outOfRangeEventsReceived);	
	}
}
